package com.majun.utils;
 
import org.springframework.util.Base64Utils;
 
import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * RSA非对称加密算法工具类
 *
 * @author majun
 * @date 2025/05/14
 */
public class RSAUtil {
    /**
     * RSA算法
     */
    private static final String RSA_ALGORITHM = "RSA";
 
    /**
     * 公私钥长度
     */
    private static final Integer KEY_LENGTH = 2048;
 
    /**
     * RSA算法公钥
     */
    private static final String RSA_PUBLIC_KEY = "RSAPublicKey";
 
    /**
     * RSA算法私钥
     */
    private static final String RSA_PRIVATE_KEY = "RSAPrivateKey";
 
    /**
     * 数据加密公钥
     */
    private static String PUBLIC_KEY = "生成后替换";
 
    /**
     * 数据解密公钥
     */
    private static String PRIVATE_KEY = "生成后替换";
 
    /**
     * 生成数据加密公钥和数据解密私钥
     * @return 数据加密公钥和解密私钥键值对
     * @throws NoSuchAlgorithmException 不匹配算法异常
     */
    private static Map<String, Key> generatePublicAndPrivateKey() throws NoSuchAlgorithmException {
        // 创建KeyPairGenerator（Java加密架构(JCA)中的一个类）实例，用于生成公钥和私钥
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
        // 初始化KeyPairGenerator实例，指定公钥和私钥长度，安全和性能综合考虑推荐2048
        keyPairGenerator.initialize(KEY_LENGTH);
        // 生成包含密钥对的实例
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        // 提取公钥
        Key publicKey = keyPair.getPublic();
        // 提取私钥
        Key privateKey = keyPair.getPrivate();
        // 创建一个初始容量位2的键值对，用于存储公私钥
        Map<String, Key> keyMap = new HashMap<>(2);
        // 将公钥放入键值对
        keyMap.put(RSA_PUBLIC_KEY, publicKey);
        // 将私钥放入键值对
        keyMap.put(RSA_PRIVATE_KEY, privateKey);
        return keyMap;
    }
 
    /**
     * 获取公钥
     * @param keyMap 公私钥键值对
     * @return 公钥字符串
     */
    private static String getPublicKey(Map<String, Key> keyMap) {
        // 从公私钥键值对中取出公钥
        Key publicKey = keyMap.get(RSA_PUBLIC_KEY);
        // 获取公钥字节数组
        byte[] publicKeyBytes = publicKey.getEncoded();
        // 使用Base64编码将公钥字节数组转换为字符串
        String publicKeyString = Base64Utils.encodeToString(publicKeyBytes);
//        PUBLIC_KEY = publicKeyString;
        return publicKeyString;

    }
 
    /**
     * 获取私钥
     * @param keyMap 公私钥键值对
     * @return 私钥字符串
     */
    private static String getPrivateKey(Map<String, Key> keyMap) {
        Key privateKey = keyMap.get(RSA_PRIVATE_KEY);
        byte[] privateKeyBytes = privateKey.getEncoded();
        String privateKeyString = Base64Utils.encodeToString(privateKeyBytes);
//        PRIVATE_KEY = privateKeyString;
        return privateKeyString;
    }
 
    /**
     * 公钥加密
     * @param needEncrypt 需要加密数据
     * @return 公钥加密后的字符串
     * @throws Exception 异常
     */
    public static String encryptByPublicKey(String needEncrypt, String publicKeyString) throws Exception {
        // 使用Base64编码将数据加密公钥转换为字节数组
        byte[] publicKeyBytes = Base64Utils.decodeFromString(publicKeyString);
        // 使用数据加密公钥字符数组创建一个X509EncodedKeySpec（密钥规范：公钥的ASN.1编码的一种表示形式）实例
        KeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyBytes);
        // 创建KeyFactory（JCE框架中的一个类）实例，用于转换公钥和私钥
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        // 从x509EncodedKeySpec（密钥规范：公钥的ASN.1编码的一种表示形式）中生成一个公钥
        // 调用generatePublic方法使用之前创建的X509EncodedKeySpec对象来生成公钥。
        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
        // 创建Cipher实例，用于加密解密
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        // 初始化Cipher实例，设置加密模式并指定要使用的公钥
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        // 加密为字节数组
        byte[] encryptBytes = cipher.doFinal(needEncrypt.getBytes());
        // 使用Base64编码将加密后的数组转换为字符串
        return Base64Utils.encodeToString(encryptBytes);
    }
 
    /**
     * 私钥解密
     * @param needDecrypt 需要解密数据
     * @return 私钥解密后的字符串
     * @throws Exception 异常
     */
    public static String decryptByPrivateKey(String needDecrypt, String privateKeyString) throws Exception {
        // 使用Base64编码将数据加密私钥转换为字节数组
        byte[] privateBytes = Base64Utils.decodeFromString(privateKeyString);
        // 使用数据解密私钥字符数组创建一个PKCS8EncodedKeySpec（密钥规范：私钥的ASN.1编码的一种表示形式）实例
        KeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateBytes);
        // 创建KeyFactory（JCE框架中的一个类）实例，用于转换公钥和私钥
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        // 从pkcs8EncodedKeySpec（密钥规范：私钥的ASN.1编码的一种表示形式）中生成一个私钥
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        // 创建Cipher实例，用于加密解密
        // 创建一个Cipher实例，它是用于加密或解密数据的对象。Cipher类提供了加密和解密功能，并支持许多不同的加密算法。
        // 在这里，getInstance 方法中传入了keyFactory.getAlgorithm()[获取与指定密钥工厂相关联的算法名称。]，它用于获取与指定算法关联的 Cipher 实例。
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        // 初始化Cipher实例，设置解密模式并指定要使用的私钥
        // 在初始化过程中，指定加密模式为 ENCRYPT_MODE，并传入了之前生成的公钥 generatePublicKey。
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        // 使用Base64编码将需要解密数据转换为字节数组
        byte[] needDecryptBytes = Base64Utils.decodeFromString(needDecrypt);
        // 解密为字节数组
        byte[] decryptBytes = cipher.doFinal(needDecryptBytes);
        // 使用Base64编码将加密后的数组转换为字符串
        return Base64Utils.encodeToString(decryptBytes);
    }
 
    /**
     * 生成数据加密公钥和数据解密私钥后，将值替换到常量PUBLIC_KEY和PRIVATE_KEY
     * @param args 启动参数
     * @throws Exception 异常
     */
    public static void main(String[] args) throws Exception {
        Map<String, Key> keyMap = generatePublicAndPrivateKey();
        String publicKey = getPublicKey(keyMap);
        String privateKey = getPrivateKey(keyMap);
        System.out.println("数据加密公钥：" + publicKey);
        System.out.println("数据解密私钥：" + privateKey);
 
        // 替换公私钥后将上方代码注释，放开下面注释，测试加密解密功能
        String data = "原始数据";
        String encrypt = encryptByPublicKey(data, publicKey);
        System.out.println("公钥加密后的数据：" + encrypt);
        String decrypt = decryptByPrivateKey(encrypt, privateKey);
        System.out.println("私钥解密后的数据：" + decrypt);
    }
}